﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Legend.Threading;

namespace Legend.Collections
{
    /// <summary>
    /// Represents a dictionary that is thread safe, also provides an ISynchronizationManager
    /// so that multiple sequential method calls can be synchronized.
    /// </summary>
    /// <typeparam name="TKey">The type of keys in the dictionary.</typeparam>
    /// <typeparam name="TValue">The type of values in the dictionary.</typeparam>
    public class SynchronizedDictionary<TKey, TValue>
        : SynchronizedCollection<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>
    {
        private IDictionary<TKey, TValue> dictionary;

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronizedDictionary&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        /// <param name="dictionary">The dictionary to synchronize.</param>
        /// <param name="synchronizationManager">The object that manages synchronization.</param>
        public SynchronizedDictionary(IDictionary<TKey, TValue> dictionary, ISynchronizationManager synchronizationManager)
            : base(dictionary, synchronizationManager)
        {
            this.dictionary = dictionary;    
        }


        /// <summary>
        /// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </summary>
        /// <param name="key">The object to use as the key of the element to add.</param>
        /// <param name="value">The object to use as the value of the element to add.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="key"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </exception>
        /// <exception cref="T:System.NotSupportedException">
        /// The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.
        /// </exception>
        public void Add(TKey key, TValue value)
        {
            using (this.AcquireWriteLock())
            {
                dictionary.Add(key, value);
            }
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key.
        /// </summary>
        /// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.</param>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the key; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="key"/> is null.
        /// </exception>
        public bool ContainsKey(TKey key)
        {
            using (this.AcquireReadLock())
            {
                return dictionary.ContainsKey(key);
            }
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </returns>
        public ICollection<TKey> Keys
        {
            get 
            {
                return new SynchronizedCollection<TKey>(dictionary.Keys, synchronizationManager);    
            }
        }

        /// <summary>
        /// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <returns>
        /// true if the element is successfully removed; otherwise, false.  This method also returns false if <paramref name="key"/> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="key"/> is null.
        /// </exception>
        /// <exception cref="T:System.NotSupportedException">
        /// The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.
        /// </exception>
        public bool Remove(TKey key)
        {
            using (this.AcquireWriteLock())
            {
                return dictionary.Remove(key);
            }
        }

        /// <summary>
        /// Gets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key whose value to get.</param>
        /// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized.</param>
        /// <returns>
        /// true if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="key"/> is null.
        /// </exception>
        public bool TryGetValue(TKey key, out TValue value)
        {
            using (this.AcquireReadLock())
            {
                return dictionary.TryGetValue(key, out value);
            }
        }

        /// <summary>
        /// Gets the value with the specified key, if the value does not exist
        /// the valueProducer delegate is called and the produced value is inserted into
        /// the dictionary and then returned.
        /// </summary>
        /// <param name="key">The key to get the value for.</param>
        /// <param name="valueProducer">Called if the key does not exist in the dictionary.</param>
        /// <returns>The value stored in the dictionary by the specified key or the value from the valueProducer delegate.</returns>
        public TValue GetValue(TKey key, Func<TValue> valueProducer)
        {
            Require.ThatArgument(key).Named("key").IsNotNull();
            Require.ThatArgument(valueProducer).Named("valueProducer").IsNotNull();

            var result = default(TValue);

            using (var readLock = this.AcquireUpgradableReadLock())
            {
                if (!this.TryGetValue(key, out result))
                {
                    result = valueProducer();

                    using (readLock.Upgrade())
                    {
                        this.dictionary.Add(key, result);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
        /// </returns>
        public ICollection<TValue> Values
        {
            get 
            {
                return new SynchronizedCollection<TValue>(dictionary.Values, synchronizationManager);
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="TValue"/> with the specified key.
        /// </summary>
        /// <value></value>
        public TValue this[TKey key]
        {
            get
            {
                using (this.AcquireReadLock())
                {
                    return dictionary[key];
                }
            }
            set
            {
                using (this.AcquireWriteLock())
                {
                    dictionary[key] = value;
                }
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
}
